<?php
if ( !defined('SESSION_LIFE') ) { 
	define('SESSION_LIFE', 3600); 
}
if ( !defined('SESSION_COOKIE_LIFE') ) { 
	define('SESSION_COOKIE_LIFE', '+1 year'); 
}
if ( !defined('SESSION_COOKIE_DOMAIN') ) { 
	define('SESSION_DOMAIN', $_SERVER['HTTP_HOST']); 
} 
if ( !defined('SESSION_COOKIE_PATH') ) { 
	define('SESSION_PATH','/'); 
} 
if ( !defined('SESSION_SERIAL') ) { 
	define('SESSION_SERIAL','/tmp/sess'); 
} 
if ( !defined('SESSION_DSN') ) { 
	define('SESSION_DSN','mysql://no_user:xxxxxx@no_host/no_database'); 
} 
if ( !defined('SESSION_INIT') ) {
	define('SESSION_INIT', Array());
}
/**************************************************
 * Minimum Database Scheme for Session Class 
CREATE TABLE sessions (
  id char(32) NOT NULL default '',
  user_id smallint(5) unsigned NOT NULL default '0',
  data text NOT NULL,
  ip varchar(15) NOT NULL default '',
  expires datetime NOT NULL default '0000-00-00 00:00:00',
  created datetime NOT NULL default '0000-00-00 00:00:00',
  modified timestamp(14) NOT NULL,
  PRIMARY KEY  (id)
) TYPE=InnoDB;

CREATE TABLE access_log (
  id int(10) unsigned NOT NULL auto_increment,
  sess_id char(32) NOT NULL default '',
  username varchar(16) NOT NULL default '',
  ip varchar(15) NOT NULL default '',
  created datetime NOT NULL default '0000-00-00 00:00:00',
  PRIMARY KEY  (id)
) TYPE=InnoDB;

 */ 

class Session {
	private function __construct() {
	}

	public static function create($sess_name, DB_Abstract $db=null) {
		if ( defined('SESSION_DATABASE') ) {
			$sess_db = Database::create(SESSION_DATABASE);
			if ( $sess_db->isConnected() ) {
				return new Session_DB($sess_name);
			}
		}

		if  ( $db != null ) {
			return new Session_DB($sess_name, $db);
		} else {
			//return new Session_FS($sess_name);
		}
		return null;
	}
}

abstract class Session_Abstract
{
	private $_sessid; 
	private $_sessname; 
	protected $_registered = array(); 

	function __construct($sess_name) { 
		$this->_sessname = $sess_name;
		$this->_registered = array_merge($this->_registered, unserialize(SESSION_INIT));
	}
	//-------------------------------------------------
	public function getSessionId() {
		return $this->_sessid;
	}
	//-------------------------------------------------
	public function getSessionName() {
		return $this->_sessname;
	}
	//-------------------------------------------------
	public function register($name,$val) { 
		$this->_registered[$name] = $val; 
	} 
	//-------------------------------------------------
	public function unregister($name) { 
		if ( isset($this->_registered[$name]) ) {
			unset($this->_registered[$name]); 
		}
	} 
	//-------------------------------------------------
	public function getVars() {
		return $this->_registered; 
	}
	//-------------------------------------------------
	public function getVar($var="") { 
		if ( isset($this->_registered[$var]) ) { 
			return $this->_registered[$var]; 
		} else {
			return '';
		}
	} 
	//-------------------------------------------------
	public function start() { 
		//Delete Past Sessions
		//$this->clear();

		if ( isset($_COOKIE[$this->_sessname]) ) { 
			//Cookie Exists, unfreeze session
 			$this->_sessid = $_COOKIE[$this->_sessname]; 
			Log::debug('Cookie: ' . $this->_sessid);

			//Unfreeze Past Session 
			if ( $this->unfreeze() ) { 
				//Security check remove session and start a new one
				//to prevent session-hijacking
				if ( $this->getVar('ip') != $_SERVER['REMOTE_ADDR'] ) {
					Log::warn('Session IP does not match Remote IP');
					$this->end();
				} 
				return; 
			} 
		} else {
			Log::debug('No Cookie Found');
			$this->generateSessId(); 
		}
	
		Log::debug('New Session Id: ' . $this->getSessionId());	
		setcookie($this->_sessname,
			  $this->_sessid,
			  strtotime(SESSION_COOKIE_LIFE));
		/* setcookie($this->_sessname,
			  $this->_sessid,
			  strtotime(SESSION_COOKIE_LIFE),
			  SESSION_COOKIE_PATH,
			  SESSION_COOKIE_DOMAIN); */
	} 
	//-------------------------------------------------
	public function freeze() {
		Log::debug('Freezing session');
		$this->register('ip',  $_SERVER['REMOTE_ADDR']);
		$this->register('expires', strtotime(SESSION_COOKIE_LIFE));
		$this->save();
	}
	//-------------------------------------------------
	public function kill() {
		$this->end();
		setcookie($this->_sessname,
			  $this->_sessid,
			  strtotime('-1 day'));
			  /* SESSION_COOKIE_PATH,
			  SESSION_COOKIE_DOMAIN); */
		$this->_sessid = '';
	}
	//-------------------------------------------------
	private function end() {
		$this->remove();
		$this->_registered = array(); 
	}
	//-------------------------------------------------
	private function generateSessId() {
                $md5_rand = $this->_sessname . $_SERVER['REMOTE_ADDR'] . rand();
                $this->_sessid = strtoupper(md5(uniqid($md5_rand, true)));
		$this->register('sessid', $this->_sessid);
	}	
	//-------------------------------------------------
	protected abstract function save();
	protected abstract function unfreeze();
	protected abstract function clear();
	protected abstract function remove();
}

class Session_DB extends Session_Abstract
{
	private $_db;
	
	function __construct($sess_name, DB_Abstract &$sess_db) { 
		parent::__construct($sess_name);
		$this->_db = $sess_db;
	} 
	//-------------------------------------------------
	public function save() {
		Log::debug('Saving session to DB');
		$userid = addslashes($this->getVar('auth_userid'));
		$expires = date('Y-m-d H:i:s', $this->getVar('expires'));
		$ip = addslashes($this->getVar('ip'));
		$serial = addslashes(serialize($this->_registered));

		//Session Updated
		$this->_db->begin();
		$update = "UPDATE sessions
			   SET data = '{$serial}',
				ip = '{$ip}',
				user_id = '{$userid}',
				expires = '{$expires}',
				modified = NOW()
			   WHERE id = '" . $this->getSessionId() . "'";

		$u = $this->_db->update($update);
		if ( $u == 0 ) {
			$insert = "INSERT INTO sessions 
					(id,data,ip,user_id,expires,created,modified)
				   VALUES ('" . $this->getSessionId() . "',
					   '{$serial}',
					   '{$ip}',
					   '{$userid}',
					   '{$expires}',
					   NOW(),
					   NOW())";
			$u = $this->_db->insert($insert);
		}

		//Track Accesses
		$this->_db->insert("INSERT INTO access_log (ip,sess_id,username,created) 
				    VALUES ('{$ip}',
					    '" . $this->getSessionId() . "',
					    '" . $this->getVar('auth_user') . "',
					    NOW())");
		$this->_db->commit();
		/*
		if ( $u == 0 ) {
			$this->_db->rollback;
		} else {
			$this->_db->commit();
		}
		*/
	}
	//-------------------------------------------------
	protected function unfreeze() {
		$sql = "SELECT data,ip,expires 
			FROM sessions 
			WHERE id = '" . $this->getSessionId() . "'"; 

		$results = $this->_db->select($sql);
		if ( $arr = $results->getRow() ) { 
			list($serial,$ip,$expires) = $arr;
			$this->_registered = 
					array_merge($this->_registered, 
							unserialize(stripslashes($serial))); 

			$this->_registered['ip'] = $ip;
			$this->_registered['expires'] = $expires;
			return true; 
		} 
		return false;
	}
	//-------------------------------------------------
	public function remove() { 
		$this->_db->update("DELETE FROM sessions 
			 	    WHERE id = '" . $this->getSessionId() . "'"); 
	}
	//-------------------------------------------------
	protected function clear() {
		//Deletes Old Sessions 
		$remove = 'DATE_SUB( NOW(),INTERVAL ' . SESSION_COOKIE_LIFE . ')';
		$this->_db->update("DELETE FROM sessions WHERE expires < {$remove}");
	}
}
 
?>
